/***********************************************************************************
 * 文件名： PortDev.c
 * 作者： 刘言
 * 版本： 1
 * 说明：
 *      以物理端口区分的设备类：端口设备。针对PTC端口和TC端口，目的是合并这2个端口上的设备操作
 *  函数。目前支持1个PTC设备：雾化器设备，2个TC设备：电池设备、一体雾化设备。
 *      本文件使用面向对象的思想设计，C++的Class将属性（变量）和函数封装在一起，C语言使用
 *  struct可以一定程度上替代Class。虽然struct也可以利用函数指针变量的方式将函数封装在struct内
 *  部，但是实例化对象时总需要重复的给这些函数指针赋值，会增加一些代码量，而且函数指针本身也会
 *  增加RAM使用量。本文件面向对象的实现方案是：在struct内只封装属性，通过调用统一的函数来操作不
 *  同的对象。损失了一定的封装性，但是节省了一定的资源，规避了 KEIL C51 编译器可能的函数指针功能
 *  不完善引起的问题。
 *      使用之前需要先实例化（定义）一个PortDev对象,然后对其进行初始化（设置基础属性）。调用函
 *  数来操作对象时必须将对象地址传递给函数（第一个参数）。
 * 		本文件提供的接口函数见文件末尾。
 * 		使用流程: 初始化后，定时调用 PortDev_Detect 检测设备是否接入，如果设备接入了，预设的
 *  回调函数将会被调用，之后可以使用 PortDev_Read PortDev_Write 来操作设备。其它函数可以在任意
 *  时刻调用。
 * 修改记录：
 * 	2020/6/15: 初版。 刘言。
***********************************************************************************/
#include "polsys.h"
#include "Board.h"
#include "DataBlock.h"
#include "PortDev.h"

//////////////////////////////// static 变量定义 /////////////////////////////////////

// 雾化器设备各个权限信息的列表
static C51_CODE const pms_info_list_t ATOM_PMS_INFO_LIST = PORTDEV_ATOM_PMS_INFO;

// 电池设备各个权限信息的列表
static C51_CODE const pms_info_list_t BATT_PMS_INFO_LIST = PORTDEV_BATT_PMS_INFO;

// 一体雾化设备各个权限信息的列表
static C51_CODE const pms_info_list_t INTE_PMS_INFO_LIST = PORTDEV_INTE_PMS_INFO;

static pdata u8 sPtcInCont = 0, sPtcOutCont = 0; // PTC接口接入检测消抖变量
static pdata u8 sTcIntCount = 0, sTcInterval = 3;             // TC接入检测间隔控制变量，成功连接后间隔1s，否则间隔0.3s

static bit sPtcLoadProcessed = false;        
static bit sPtcUnloadProcessed = false;
static pdata PortDev_callback_t sPtcLoadCallback = NULL;       // 装载事件的回调函数
static pdata PortDev_callback_t sPtcUnloadCallback = NULL;     // 卸载事件的回调函数

static bit sTcLoadProcessed = false;        
static bit sTcUnloadProcessed = false;
static pdata PortDev_callback_t sTcLoadCallback = NULL;       // 装载事件的回调函数
static pdata PortDev_callback_t sTcUnloadCallback = NULL;     // 卸载事件的回调函数

//////////////////////////////// 私有函数声明 ////////////////////////////////

static void ApplyPermission(PortDev *obj, u8 local_cid);
static bool HaveData(u8 *buff, u8 len);
static bool TryConnect(PortDev *obj);
static void DisConnect(PortDev *obj);
static bool HaveOverlap(const pms_info_t *pPmsInfo, u16 start_addr, u16 end_addr);
static bool CheckWritePermission(PortDev *obj, u16 addr, u16 len);

//////////////////////////////// 接口（public）函数定义 ////////////////////////////////

// 初始化 PortDev 对象
// obj: 操作的对象
// protocol: 设备使用的通信协议 取值见 protocol_t 。
void PortDev_Init(PortDev *obj, protocol_t protocol)
{
    TDevCom_Init((TDevCom *)obj, protocol, 0); // 父类对象应该在最开始就可以被初始化，设定了设备使用的通信协议
    if (protocol == TDEVCOM_ATOMIZER)           // 雾化器通信协议目前只有1个设备（雾化器）在使用，这里给了初始值后以后就不用修改了
    {
        obj->pPmsInfoList = &ATOM_PMS_INFO_LIST; // 设备对象的权限信息列表
    }
    // else //if (protocol == TDEVCOM_BATTERY_INTEGRATED)  // 电池和一体设备通信协议目前有2个设备（电池、一体设备）在使用
    // {
    //     obj->pPmsInfoList = &BATT_PMS_INFO_LIST;   // 先给个电池设备的默认值，建立通信后会重新赋值
    // }
    // obj->mPermission = 0xff;
    // obj->mCustID = 1;         // 客户ID
    // obj->mHaveData = false;     // 内部是否有数据标志。
}

// 设置物理接口的回调函数
IN_LINE void PortDev_SetCallBacks(PortDev_callback_t ptc_loadcallback, 
                                    PortDev_callback_t ptc_unloadcallback,
                                    PortDev_callback_t tc_loadcallback,
                                    PortDev_callback_t tc_unloadcallback)
{
    sPtcLoadCallback = ptc_loadcallback;
    sPtcUnloadCallback = ptc_unloadcallback;
    sTcLoadCallback = tc_loadcallback;
    sTcUnloadCallback = tc_unloadcallback;
}

// 获取错误码
// obj: 操作的对象
IN_LINE u8 PortDev_GetErrorCode(PortDev *obj)
{
    return (u8)obj->Parent.mErrorCode;
}

#ifdef USE_WRITE_VERIFY
//雾化器写入数据：将buff里面的数据写入到雾化器地址addr处，长度是len
static bool WriteNoVerify(PortDev *obj, u16 addr, u16 len, const u8 *buff)
#else
// 写入数据。
// obj: 操作的对象
// addr 设备存储空间的地址
// len 数据字节数
// buff 要写的数据
bool PortDev_Write(PortDev *obj, u16 addr, u16 len, const u8 *buff)
#endif
{
    idata u8 per_len;
    bit ret = true;

    if (CheckWritePermission(obj, addr, len) == false)
    {
        obj->Parent.mErrorCode = TDEVCOM_NO_PERMISSION;
        return false;
    }
    while (len > 0)
    {
        if (len > 128)
            per_len = 128;
        else
            per_len = len;
        ret = TDevCom_Write((TDevCom *)obj, addr, buff, per_len); //一次最多写入128字节
        if (ret != true)
        {
            break;
        }
        len -= per_len;  //剩余长度
        buff += per_len; //剩余未写入的数据的缓存地址
        addr += per_len; //剩余未写入的雾化器数据地址
    }

    return ret;
}

#ifdef USE_WRITE_VERIFY
//校验：从地址addr开始长度为len的数据是否与buff中数据一致
static bool Verify(u16 addr, u16 len, const u8 *buff)
{
    xdata u8 Buff[128];
    u8 i, per_len;
    bool ret = true;

    while (len > 0)
    {
        if (len > 128)
            per_len = 128;
        else
            per_len = len;
        TDevCom_Read(&AtomCom, addr, Buff, per_len); //一次最多读取128字节
        for (i = 0; i < per_len; i++)
        {
            if (Buff[i] != buff[i])
            {
                ret = false;
                break;
            }
        }
        len -= per_len;  //剩余长度
        buff += per_len; //剩余未校验的数据的缓存地址
        addr += per_len; //剩余未校验的雾化器数据地址
    }

    return ret;
}

//向雾化器写入数据并进行校验是否成功写入。
//addr 雾化器存储空间的地址
//len 数据字节数
//buff 要写的数据
bool PortDev_Write(u16 addr, u16 len, const u8 *buff)
{
    bit ret;

    if (CheckWritePermission(addr, len) == false)
    {
        //mLastResult = PTC_NO_PERMISSION;
        return false;
    }
    if (WriteNoVerify(addr, len, buff)) //如果写入操作完成
    {
        ret = Verify(addr, len, buff); //校验
    }
    else
        ret = false;

    return ret;
}

#endif

// 从设备中读取数据
// obj: 操作的对象
// addr 设备内部存储器地址
// len 长度
// buff 读出的数据存放处
bool PortDev_Read(PortDev *obj, u16 addr, u16 len, u8 *buff)
{
    u8 per_len;
    bit ret = true;

    while (len > 0)
    {
        if (len > 128)
            per_len = 128;
        else
            per_len = len;
        ret = TDevCom_Read((TDevCom *)obj, addr, buff, per_len); //一次最多读取128字节
        if (ret != true)
        {
            break;
        }
        len -= per_len;  //剩余长度
        buff += per_len; //剩余未填充数据的缓存地址
        addr += per_len; //剩余未读取的雾化器数据地址
    }

    return ret;
}

// 识别设备。获取设备信息（权限信息列表、客户ID、是否加密、是否有数据、操作权限，将来可能会加入设备类型、版本等）。
// 以及更新数据块的直接地址。
// obj 操作的对象
// local_cid 本机客户ID（CID）
bool PortDev_Recognition(PortDev *obj, u8 local_cid)
{
    xdata u8 info_data[256];
    
    if (PortDev_Read(obj, 0, 256, info_data)) // 读取前256字节数据,设备信息数据
    {
        device_type_t type = (device_type_t)info_data[PORTDEV_DEVICE_TYPE_ADDR];
        if (type == PORTDEV_BATTERY) // 这是电池设备
        {
            obj->pPmsInfoList = &BATT_PMS_INFO_LIST;    // 使用电池的权限信息列表
            obj->mCustID = info_data[BATT_CUSTID_ADDR]; // 客户ID
            //TDevCom_SetEncrypt((TDevCom *)obj, false, NULL); // 不加密通信 （每次建立连接默认是不加密的）
            obj->mHaveData = HaveData(info_data + BATT_HAVE_DATA_ADDR, 4);
        }
        else if (type == PORTDEV_INTEGRATED) // 这是一体设备
        {
            obj->pPmsInfoList = &INTE_PMS_INFO_LIST;                                     // 使用一体设备的权限信息列表
            obj->mCustID = info_data[INTE_CUSTID_ADDR];                                  // 客户ID
            TDevCom_SetEncrypt((TDevCom *)obj, true, info_data + PORTDEV_RAND_NUM_ADDR); // 加密通信，函数内部计算回应码
            obj->mHaveData = HaveData(info_data + INTE_HAVE_DATA_ADDR, 4);
        }
        else //if(type == PORTDEV_ATOMIZER)   // 这是雾化器设备
        {
            // 雾化器设备使用的通信协议和接口仅支持雾化器设备，在对象初始化（构造）设置通信协议与接口时
            // 已经设置好了 pPmsInfoList 是否加密等属性，这里无需再重复设置。
            obj->mCustID = info_data[ATOM_CUSTID_ADDR]; // 客户ID
            obj->mHaveData = HaveData(info_data + ATOM_HAVE_DATA_ADDR, 4);
        }
        ApplyPermission(obj, local_cid);             // 刷新对接入设备的操作权限
        DB_UpdataBlockDAddr(info_data);    // 更新数据块的直接地址
    }
    else
    {
        return false;
    }
    return true;
}

// 重新装载设备（软件模拟设备重新装入）
// obj: 操作的对象
void PortDev_Reload(PortDev *obj)
{
    //obj->mLoadProcessed = false;
    if (obj->Parent.mProtocol == TDEVCOM_ATOMIZER) // PTC接口
    {
        sPtcInCont = 0;
        sPtcOutCont = 0;
        sPtcLoadProcessed = false;
    }
    else // TC接口
    {
        sTcIntCount = 0;
        sTcInterval = 3;
        sTcLoadProcessed = false;
    }
}

// 检测设备是否接入，如果接入了调用事件回调函数，建议间隔100ms循环调用来不断检测
// obj: 操作的对象
void PortDev_Detect(PortDev *obj)
{
    if (obj->Parent.mProtocol == TDEVCOM_ATOMIZER) // PTC接口
    {
        if (Board_ATCheckIn()) //雾化器装入了
        {
            if (sPtcInCont < 255)
                sPtcInCont++;
            if (sPtcInCont == 4) //装好了,真的装入了
            {
                sPtcOutCont = 0;
                if (sPtcLoadProcessed == false) //新装入的(装入未处理)
                {
                    if (sPtcLoadCallback != NULL)
                    {
                        TryConnect(obj);
                        sPtcLoadProcessed = sPtcLoadCallback(); //调用回调函数
                        DisConnect(obj);
                    }
                }
                sPtcUnloadProcessed = false;
            }
        }
        else //雾化器取下了
        {
            if (sPtcOutCont < 255)
                sPtcOutCont++;
            if (sPtcOutCont == 2) //真的取下了
            {
                sPtcInCont = 0;
                if (sPtcUnloadProcessed == false)
                {
                    if (sPtcUnloadCallback != NULL)
                        sPtcUnloadProcessed = sPtcUnloadCallback();
                }
                sPtcLoadProcessed = false;
            }
        }
    }
    else // TC接口
    {
        sTcIntCount++;
        if (sTcIntCount == sTcInterval)
        {
            sTcIntCount = 0;
            if (TryConnect(obj)) //同步成功，已进入通信模式，说明接入了电池
            {
                if (sTcLoadProcessed == false) //本次装入未处理
                {
                    if (sTcLoadCallback != NULL)
                        sTcLoadProcessed = sTcLoadCallback(); //mLoadCallback返回true，不再处理本次装入
                }
                DisConnect(obj); //已经处理过，直接断开通信
                sTcInterval = 10;   //1秒后再检测,电池需要1秒后才能再准备好建立连接
                sTcUnloadProcessed = false;
            }
            else //无法建立同步，说明未接入电池
            {
                if (sTcUnloadProcessed == false)
                {
                    if (sTcUnloadCallback != NULL)
                        sTcUnloadProcessed = sTcUnloadCallback();
                }
                sTcLoadProcessed = false;
                sTcInterval = 3; //0.3秒后再检测
            }
        }
    }
}

//////////////////////////////// 私有函数定义 ////////////////////////////////

// 刷新操作权限，本机对不同的设备（中的CID）有不同的操作权限
// obj: 操作的对象
// local_cid: 本机客户ID（CID）
static void ApplyPermission(PortDev *obj, u8 local_cid)
{
    if (local_cid == 0) // 0 代表设备制造商，拥有最高权限
    {
        obj->mPermission = 0;
    }
    else
    {
        if (local_cid == obj->mCustID)
            obj->mPermission = 1; // 本机（读写器）CID与装载的设备CID一致（同一家油品商）
        else
            obj->mPermission = 9; // 不是同一家油品商或者不是油品商，只有最低操作权限
    }
}

//判断缓存中是否有数据
static bool HaveData(u8 *buff, u8 len)
{
    u8 i;
    for (i = 0; i < len; i++)
    {
        if (buff[i] != 0xff)
            return true;
    }
    return false;
}

// 尝试连接
static bool TryConnect(PortDev *obj)
{
    u8 i;

    for (i = 0; i < 2; i++)
    {
        if (TDevCom_Enter((TDevCom *)obj)) //尝试建立同步，进入通信模式
        {
            // mConected = true;
            return true;                       // 设备连接成功
        }
        TDevCom_Exit((TDevCom *)obj);
        if (i < 1)
            Delay_ms(1000);
    }
    return false;
}

// 断开连接
IN_LINE static void DisConnect(PortDev *obj)
{
    TDevCom_Exit((TDevCom *)obj);
    //mConected = false;
}

// 指定地址范围是否与特定权限的数据范围有重叠，重叠包含3中情况：完全在里面、前端在里面、后端在里面。
// pPmsInfo 权限信息数据结构的指针，要判断的权限
// start_addr 地址范围开始
// end_addr 地址范围结束
// 返回 true 重叠
static bool HaveOverlap(const pms_info_t *pPmsInfo, u16 start_addr, u16 end_addr)
{
    u8 i;
    for (i = 0; i < pPmsInfo->DbNum; i++) // 循环判断该权限的每一个数据块是否有重叠
    {
        if (IsInRangeU16(start_addr, pPmsInfo->StartAddr[i], pPmsInfo->EndAddr[i]))
            return true;                              // 开始地址在范围内，肯定有重叠
        else if (start_addr < pPmsInfo->StartAddr[i]) // 开始地址在不可写块之前
        {
            if (end_addr >= pPmsInfo->StartAddr[i])
                return true; // 结束地址落在了不可写区域的开始地址以后，有重叠
        }
    }
    return false;
}

// 检查是否有写入权限
// 返回 false 表示地址不可操作（无权限）
static bool CheckWritePermission(PortDev *obj, u16 addr, u16 len)
{
    u16 endaddr = addr + len - 1;

    if (obj->mPermission == 0)
        return true;
    else if (obj->mPermission == 1) //同一家油品商，不可以修改权限0的区域
    {
        if (HaveOverlap(&obj->pPmsInfoList->Dir.P0, addr, endaddr))
            return false; //要写入的地址与 权限0 的数据范围有重叠，返回不可操作
    }
    else if (obj->mPermission == 9) //不是同一家油品商或者不是油品商，只能修改权限9的区域（不可以修改权限0~8的区域）
    {
        if ((addr < obj->pPmsInfoList->Dir.P9.StartAddr[0]) || (endaddr > obj->pPmsInfoList->Dir.P9.EndAddr[0]))
            return false; // 要写入的地址超出了 权限9 的数据范围，返回不可操作
    }
    return true;
}
